//
// This file taken from http://www.codeproject.com/csharp/notifyballoon.asp
// Also see http://www.gotdotnet.ru/Forums/Common/209808.aspx
//
using System;
using System.ComponentModel;
using System.Drawing;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Windows.Forms;


namespace IconEx
{
	public class NotifyIconEx : Component
	{
		#region Notify Icon Target Window

		private class NotifyIconTarget : Form
		{
			protected override void DefWndProc (ref Message msg)
			{
				if (msg.Msg == 0x400) // WM_USER
				{
					uint msgId = (uint) msg.LParam;
					uint id = (uint) msg.WParam;

					switch (msgId)
					{
						case 0x201: // WM_LBUTTONDOWN
							break;

						case 0x202: // WM_LBUTTONUP
							if (ClickNotify != null)
								ClickNotify (this, id);
							break;

						case 0x203: // WM_LBUTTONDBLCLK
							if (DoubleClickNotify != null)
								DoubleClickNotify (this, id);
							break;

						case 0x205: // WM_RBUTTONUP
							if (RightClickNotify != null)
								RightClickNotify (this, id);
							break;

						case 0x200: // WM_MOUSEMOVE
							break;

						case 0x402: // NIN_BALLOONSHOW
							break;

							// this should happen when the balloon is closed using the x
							// - we never seem to get this message!
						case 0x403: // NIN_BALLOONHIDE
							break;

							// we seem to get this next message whether the balloon times
							// out or whether it is closed using the x
						case 0x404: // NIN_BALLOONTIMEOUT
							break;

						case 0x405: // NIN_BALLOONUSERCLICK
							if (ClickBalloonNotify != null)
								ClickBalloonNotify (this, id);
							break;
					}
				}
				else if (msg.Msg == 0xC086) // WM_TASKBAR_CREATED
				{
					if (TaskbarCreated != null)
						TaskbarCreated (this, EventArgs.Empty);
				}
				else
				{
					base.DefWndProc (ref msg);
				}
			}

			public delegate void NotifyIconHandler(object sender, uint id);

			public event NotifyIconHandler ClickNotify;
			public event NotifyIconHandler DoubleClickNotify;
			public event NotifyIconHandler RightClickNotify;
			public event NotifyIconHandler ClickBalloonNotify;
			public event EventHandler TaskbarCreated;
		}

		#endregion

		#region Platform Invoke

		[StructLayout (LayoutKind.Sequential)]
		private struct NotifyIconData
		{
			public UInt32 cbSize; // DWORD
			public IntPtr hWnd; // HWND
			public UInt32 uID; // UINT
			public NotifyFlags uFlags; // UINT
			public UInt32 uCallbackMessage; // UINT
			public IntPtr hIcon; // HICON
			[MarshalAs (UnmanagedType.ByValTStr, SizeConst=128)] public String szTip; // char[128]
			public NotifyState dwState; // DWORD
			public NotifyState dwStateMask; // DWORD
			[MarshalAs (UnmanagedType.ByValTStr, SizeConst=256)] public String szInfo; // char[256]
			public UInt32 uTimeoutOrVersion; // UINT
			[MarshalAs (UnmanagedType.ByValTStr, SizeConst=64)] public String szInfoTitle; // char[64]
			public NotifyInfoFlags dwInfoFlags; // DWORD
		}

		[DllImport ("shell32.Dll")]
		private static extern Int32 Shell_NotifyIcon (NotifyCommand cmd, ref NotifyIconData data);

		[DllImport ("User32.Dll")]
		private static extern Int32 TrackPopupMenuEx (IntPtr hMenu,
		                                              UInt32 uFlags,
		                                              Int32 x,
		                                              Int32 y,
		                                              IntPtr hWnd,
		                                              IntPtr ignore);

		[StructLayout (LayoutKind.Sequential)]
		private struct POINT
		{
			public Int32 x;
			public Int32 y;
		}

		[DllImport ("User32.Dll")]
		private static extern Int32 GetCursorPos (ref POINT point);

		[DllImport ("User32.Dll")]
		private static extern Int32 SetForegroundWindow (IntPtr hWnd);

		#endregion

		public enum NotifyInfoFlags
		{
			None = 0x00,
			Info = 0x01,
			Warning = 0x02,
			Error = 0x03,
			User = 0x04
		}

		private enum NotifyCommand
		{
			Add = 0x00,
			Delete = 0x02,
			Modify = 0x01
		}

		private enum NotifyFlags
		{
			Message = 0x01,
			Icon = 0x02,
			Tip = 0x04,
			Info = 0x10,
			State = 0x08
		}

		private enum NotifyState
		{
			Hidden = 0x01
		}

		private uint m_id = 0; // each icon in the notification area has an id
		private IntPtr m_handle; // save the handle so that we can remove icon
		private static NotifyIconTarget m_messageSink = new NotifyIconTarget();
		private static uint m_nextId = 1;
		private string m_text = "";
		private Icon m_icon = null;
		private ContextMenu m_contextMenu = null;
		private bool m_visible = false;
		private bool m_doubleClick = false; // fix for extra mouse up message we want to discard

		public event EventHandler Click;
		public event EventHandler DoubleClick;
		public event EventHandler BalloonClick;

		#region Properties

		public string Text
		{
			set
			{
				if (m_text != value)
				{
					m_text = value;
					CreateOrUpdate();
				}
			}
			get { return m_text; }
		}

		public Icon Icon
		{
			set
			{
				m_icon = value;
				CreateOrUpdate();
			}
			get { return m_icon; }
		}

		public ContextMenu ContextMenu
		{
			set { m_contextMenu = value; }
			get { return m_contextMenu; }
		}

		public bool Visible
		{
			set
			{
				if (m_visible != value)
				{
					m_visible = value;
					CreateOrUpdate();
				}
			}
			get { return m_visible; }
		}

		#endregion

		// this method adds the notification icon if it has not been added and if we
		// have enough data to do so
		private void CreateOrUpdate( )
		{
			if (DesignMode)
				return;

			if (m_id == 0)
			{
				if (m_icon != null)
				{
					// create icon using available properties
					Create (m_nextId++);
				}
			}
			else
			{
				// update notify icon
				Update();
			}
		}

		private void Create (uint id)
		{
			NotifyIconData data = new NotifyIconData();
			data.cbSize = (uint) Marshal.SizeOf (data);

			m_handle = m_messageSink.Handle;
			data.hWnd = m_handle;
			m_id = id;
			data.uID = m_id;

			data.uCallbackMessage = 0x400;
			data.uFlags |= NotifyFlags.Message;

			data.hIcon = m_icon.Handle; // this should always be valid
			data.uFlags |= NotifyFlags.Icon;

			data.szTip = m_text;
			data.uFlags |= NotifyFlags.Tip;

			if (!m_visible)
				data.dwState = NotifyState.Hidden;
			data.dwStateMask |= NotifyState.Hidden;

			Shell_NotifyIcon (NotifyCommand.Add, ref data);

			// add handlers
			m_messageSink.ClickNotify += new NotifyIconTarget.NotifyIconHandler (OnClick);
			m_messageSink.DoubleClickNotify += new NotifyIconTarget.NotifyIconHandler (OnDoubleClick);
			m_messageSink.RightClickNotify += new NotifyIconTarget.NotifyIconHandler (OnRightClick);
			m_messageSink.ClickBalloonNotify += new NotifyIconTarget.NotifyIconHandler (OnClickBalloon);
			m_messageSink.TaskbarCreated += new EventHandler (OnTaskbarCreated);
		}

		// update an existing icon
		private void Update( )
		{
			NotifyIconData data = new NotifyIconData();
			data.cbSize = (uint) Marshal.SizeOf (data);

			data.hWnd = m_messageSink.Handle;
			data.uID = m_id;

			data.hIcon = m_icon.Handle; // this should always be valid
			data.uFlags |= NotifyFlags.Icon;

			data.szTip = m_text;
			data.uFlags |= NotifyFlags.Tip;
			data.uFlags |= NotifyFlags.State;

			if (!m_visible)
				data.dwState = NotifyState.Hidden;
			data.dwStateMask |= NotifyState.Hidden;

			Shell_NotifyIcon (NotifyCommand.Modify, ref data);
		}

		protected override void Dispose (bool disposing)
		{
			Remove();
			base.Dispose (disposing);
		}

		public void Remove( )
		{
			if (m_id != 0)
			{
				// remove the notify icon
				NotifyIconData data = new NotifyIconData();
				data.cbSize = (uint) Marshal.SizeOf (data);

				data.hWnd = m_handle;
				data.uID = m_id;

				Shell_NotifyIcon (NotifyCommand.Delete, ref data);

				m_id = 0;
			}
		}


		public void ShowBalloon (string title, string text, NotifyInfoFlags type, int timeoutInMilliSeconds)
		{
			if (timeoutInMilliSeconds < 0)
				throw new ArgumentException ("The parameter must be positive", "timeoutInMilliseconds");

			NotifyIconData data = new NotifyIconData();
			data.cbSize = (uint) Marshal.SizeOf (data);

			data.hWnd = m_messageSink.Handle;
			data.uID = m_id;

			data.uFlags = NotifyFlags.Info;
			data.uTimeoutOrVersion = (uint) timeoutInMilliSeconds; // This value works only when user is non-idle
			data.szInfoTitle = title;
			data.szInfo = text;
			data.dwInfoFlags = type;

			Shell_NotifyIcon (NotifyCommand.Modify, ref data);
		}


		public void HideBallon()
		{
			ShowBalloon ("", "", NotifyInfoFlags.None, 0);			
		}


		#region Message Handlers

		private void OnClick (object sender, uint id)
		{
			if (id == m_id)
			{
				if (!m_doubleClick && Click != null)
					Click (this, EventArgs.Empty);
				m_doubleClick = false;
			}
		}

		private void OnRightClick (object sender, uint id)
		{
			if (id == m_id)
			{
				// show context menu
				if (m_contextMenu != null)
				{
					POINT point = new POINT();
					GetCursorPos (ref point);

					SetForegroundWindow (m_messageSink.Handle); // this ensures that if we show the menu and then click on another window the menu will close

					// call non public member of ContextMenu
					m_contextMenu.GetType().InvokeMember ("OnPopup",
					                                      BindingFlags.NonPublic | BindingFlags.InvokeMethod | BindingFlags.Instance,
					                                      null, m_contextMenu, new Object[] {EventArgs.Empty});

					TrackPopupMenuEx (m_contextMenu.Handle, 64, point.x, point.y, m_messageSink.Handle, IntPtr.Zero);

					// PostMessage(m_messageSink.Handle, 0, IntPtr.Zero, IntPtr.Zero);
				}
			}
		}

		private void OnDoubleClick (object sender, uint id)
		{
			if (id == m_id)
			{
				m_doubleClick = true;
				if (DoubleClick != null)
					DoubleClick (this, EventArgs.Empty);
			}
		}

		private void OnClickBalloon (object sender, uint id)
		{
			if (id == m_id)
				if (BalloonClick != null)
					BalloonClick (this, EventArgs.Empty);
		}

		private void OnTaskbarCreated (object sender, EventArgs e)
		{
			if (m_id != 0)
				Create (m_id); // keep the id the same
		}

		#endregion
	}
}